from generate_train_data import generate_train_test_local
from keras.callbacks import Callback
from keras.layers import Input, CuDNNLSTM, Dropout
from keras.layers import Dense
from keras.models import Model
from keras.optimizers import Adam
from keras.utils import multi_gpu_model
from keras import backend as K
from LossHistory import LossHistory
from pyserverchan import pyserver


class MyCbk(Callback):
    def __init__(self, model):
        self.model_to_save = model

    def on_epoch_end(self, epoch, logs=None):
        self.model_to_save.save('../model/tenpai_epoch_%d.model' % epoch)

def acc(y_true, y_pred):
    y_true = K.l2_normalize(y_true, axis=-1)
    #辅助解决过拟合
    """
    在指定的轴使用 L2 范式标准化一个张量。
    y_true：张量或变量。
    axis：需要执行标准化的轴。
    """
    y_pred = K.l2_normalize(y_pred, axis=-1)
    return K.sum(y_true * y_pred, axis=-1)
    #沿着指定轴计算张量之和
    #axis = [-2,2),-1=1，横轴


if __name__ == "__main__":
    #x_train, x_test, y_train, y_test = generate_train_test()
    #x_train, x_test, y_train, y_test = generate_train_test_local()
    x_train, x_test, y_train, y_test = test_test()
    #获取训练数据
    #print(x_train.shape)
    #print(y_train.shape)

    # Model
    inp = Input(shape=(x_train[0].shape[0], 52))
    #52维向量
    x = CuDNNLSTM(  256,# 输出的维度
                    return_sequences=True  )(inp)
    """
    第一项units，输出空间的维度，2^8
    return_sequences决定返回输出序列的最后一个输出还是全部序列
    层与层的输数出通过return_sequences = True参数修改成3D数据，以便供下一层网络使用。
    """
    #输出的hidden state 包含全部时间步的结果。
    #使用CuDNN-LSTM代替LSTM，它的训练速度提高了15倍。CuDNN-LSTM由CuDNN支持，只能在GPU上运行。
    x = Dropout(0.1)(x)
    #激活函数
    #在前向传导的时候，让某个神经元的激活值以一定的概率p，让其停止工作
    """
    x = CuDNNLSTM(256, return_sequences=True)(x)
    x = Dropout(0.1)(x)
    x = CuDNNLSTM(256, return_sequences=True)(x)
    x = Dropout(0.1)(x)
    """
    x = CuDNNLSTM(256, return_sequences=False)(x)
    
    x = Dropout(0.1)(x)
    x = Dense(128, activation="relu")(x)
    #全连接层
    #activation激活函数.线性整流函数
    
    x = Dropout(0.1)(x)
    x = Dense(64, activation="relu")(x)
    #全连接层
    #activation激活函数.线性整流函数
    x = Dropout(0.1)(x)
    output = Dense(34, activation="softmax")(x)
    #全连接层
    #activation激活函数.
    #对输入数据的最后一维进行softmax，用在输出层，辅助实现多分类。
    model = Model(inputs=inp, outputs=output)
    #目前的output拥有一维softmax
    
    par_model = multi_gpu_model(model, gpus=2)
    #将模型在多个GPU上复制,2个
    #将一个网络在多块GPU中进行初始化，并且将一个batch的数据分配到不同的GPU中
    #反向传播结束后，再使用CPU进行合并更新参数。
    opt = Adam(lr=0.001, beta_1=0.9, beta_2=0.999,
               epsilon=None, decay=0.0, amsgrad=False)
    #替代 SGD 过程的一阶优化，适应性矩估计。
    #ICLR 论文（Adam: A Method for Stochastic Optimization）中提出
    #lr：float> = 0.学习率。
    #beta_1：float，0<beta<1；接近1。一阶矩估计的指数衰减率。
    #beta_2：float，0<beta<1；接近1。二阶矩估计的指数衰减率。
    #epsilon：float>= 0.模糊因子。如果None，默认为K.epsilon()，除零弥补修正。
    #decay：浮动>=0.每次更新时学习率会下降。
    #adagrad：自适应梯度，在此测试中决定不启用。
    """
    Which kind of loss to use?
    We should write another metrics
    """
    par_model.compile(#loss='cosine_proximity',
                      loss='categorical_crossentropy',
                      optimizer=opt,
                      metrics=['categorical_crossentropy', acc])
    #loss:目标函数/损失函数，在此采用分类损失交叉熵
    #优化器选择Adam
    #metrics: 列表，包含评估模型在训练和测试时的性能的指标
    #acc自定义accuracy:training精度，作为是否收敛的参考。
    print(model.summary())
    #这是原model
    #输出模型各层的参数状况

    epoch_nb = 70
    batch = 512
    #一个时期意味已经训练了所有数据集(所有记录)一次
    #所选的历元数为4
    #每批（不是每个时期）选取512个记录
    cbk = MyCbk(model)
    #加载save_model
    history = LossHistory()
    #从history分别将acc，val_acc,loss,val_loss提取出来
    #加了val前缀的是每个epoche所得到的模型丢测试集得到的结果。
    
    par_model.fit(  x_train, y_train, 
                    batch_size=batch, epochs=epoch_nb,
                    verbose=1, 
                    validation_data=(x_test, y_test), 
                    callbacks=[cbk, history] )
    history.loss_plot()
    #分布在2个GPU，每个GPU处理batch/2个样本，

    model.save("../model/tenpai.model")
    svc = pyserver.ServerChan()
    svc.output_to_weixin('Tenpai train done.')
    
    """
    每一个 batch 的样本都是独立并行处理的。
    在训练时，一个 batch 的结果只会用来更新一次模型。
    一个 batch 的样本通常比单个输入更接近于总体输入数据的分布，batch 越大就越近似。
    """
    """
    Epoch: 轮次，通常被定义为 「在整个数据集上的一轮迭代」，用于训练的不同的阶段，这有利于记录和定期评估。
    """
    
    
    
    
    
    

